package cn.ithers.blog.service.impl;

import cn.hutool.core.util.RandomUtil;
import cn.hutool.core.util.StrUtil;
import cn.ithers.blog.dao.ArticleDao;
import cn.ithers.blog.dao.ArticleNoAllDao;
import cn.ithers.blog.dao.CommentDao;
import cn.ithers.blog.dao.LabelDao;
import cn.ithers.blog.domain.PageList;
import cn.ithers.blog.dto.ArticleDTO;
import cn.ithers.blog.dto.UpdateArticleDTO;
import cn.ithers.blog.pojo.Article;
import cn.ithers.blog.pojo.ArticleNoAll;
import cn.ithers.blog.pojo.Label;
import cn.ithers.blog.pojo.User;
import cn.ithers.blog.response.ResponseResult;
import cn.ithers.blog.service.IArticleService;
import cn.ithers.blog.service.IUserService;
import cn.ithers.blog.utils.Constants;
import cn.ithers.blog.utils.IdWorker;
import cn.ithers.blog.utils.RedisUtils;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.stereotype.Service;

import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import javax.transaction.Transactional;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;

/**
 * @Author: zhanghuan
 * @date: 2020/10/6 7:58
 * @description:
 */
@Slf4j
@Transactional
@Service
public class ArticleServiceImpl extends BaseService implements IArticleService {

    @Autowired
    private IUserService userService;
    @Autowired
    private IdWorker idWorker;

    @Autowired
    private ArticleDao articleDao;

    @Autowired
    private ArticleNoAllDao articleNoAllDao;

    @Autowired
    private LabelDao labelDao;

    @Autowired
    private CommentDao commentDao;

    @Autowired
    private RedisUtils redisUtils;

    @Autowired
    private Gson gson;


    /**
     * 发布文章
     * 后期做一个定时发布
     * <p>
     * 如果是多人博客可以考虑审核 -- 审核之后有一个通知
     * <p>
     * 保存草稿
     * 1.用户手动提交->会发生页面跳转
     * 2.代码自动提交  每隔一短时间就会提交 ：不发生页码跳转=》有可能会多次提交=》如果没有唯一标识，就会重添加到数据库中
     * 不管是哪种草稿，必须有标题
     * 方案一、每次用户发新文章之前-》先向后台请求一个文章id
     * 如果是更新文章，则不需要请求这个唯一id.这样提交文章就携带id,避免重复添加到数据库
     * 方案二、直接提交，后台判断有没有id,如过没有id,就创建，并且id作为返回结果，有id就修改
     * <p>
     * 推荐：
     * 自动保存草稿，在前端本地完成，也就是保存在本地，
     * 如果是用户手动提交的，就提交到后台
     * 防止重复提交 ：1.可以通过id的方式。2.通过token_key的提交频率来计算，如果是30秒内有多次提交，只有最前的一次有效
     * <p>
     * 前端处理：点击了提交以后，禁止按钮可以使用等到有响应结果，在改变按钮的状态
     *
     * @param articleDTO
     * @return
     */
    @Override
    public ResponseResult postArticle(ArticleDTO articleDTO) {

        //检查用户，获取用户对象
        User user = userService.checkUser();
        if (user == null) {
            return ResponseResult.ACCOUNT_NOT_LOGIN();
        }

        //检查数据 title,分类id,内容，类型，摘要，标签
        String title = articleDTO.getTitle();
        if (StrUtil.isEmpty(title)) {
            return ResponseResult.FAILED("标题不可为空");
        }
        //2种 状态，草稿和发布
        String state = articleDTO.getState();
        if (!Constants.Article.STATE_PUBLISH.equals(state) && !Constants.Article.STATE_DRAFT.equals(state)) {
            return ResponseResult.FAILED("不支持此操作");
        }
        String type = articleDTO.getType();
        if (StrUtil.isEmpty(type)) {
            return ResponseResult.FAILED("类型不可为空");
        }
        if (!"0".equals(type) && !"1".equals(type)) {
            return ResponseResult.FAILED("类型错误");
        }

        //以下是发布的检查，草稿不需要检查
        //state = "1"
        if (Constants.Article.STATE_PUBLISH.equals(state)) {

            if (title.length() > Constants.Article.TITLE_MAX_LENGTH) {
                return ResponseResult.FAILED("标题长度不能超过" + Constants.Article.TITLE_MAX_LENGTH + "个字符");
            }
            String content = articleDTO.getContent();
            if (StrUtil.isEmpty(content)) {
                return ResponseResult.FAILED("内容不可为空");
            }

            String summary = articleDTO.getSummary();
            if (summary != null) {
                log.info("summary.length-------------->" + summary.length());
                if (summary.length() > Constants.Article.SUMMARY_MAX_LENGTH) {
                    return ResponseResult.FAILED("摘要长度不可以超过" + Constants.Article.SUMMARY_MAX_LENGTH + "个字符");
                }
            }

            String label = articleDTO.getLabel();
            //标签-标签1-标签2
            if (StrUtil.isEmpty(label)) {
                return ResponseResult.FAILED("标签不可为空");
            }
        }

        Article article = new Article();
        String articleId = articleDTO.getId();
        if (StrUtil.isEmpty(articleId)) {
            //新内容，数据库里没有
            //补充数据:Id,创建时间，用户id,更新时间
            article.setId(idWorker.nextId() + "");
            article.setCreateTime(new Date());

        } else {
            //更新内容，对状态进行处理，如果是发布的，则不能在保存为草稿
            Article articleFromDb = articleDao.findOneById(articleId);
            if (Constants.Article.STATE_PUBLISH.equals(articleFromDb.getState()) &&
                    Constants.Article.STATE_DRAFT.equals(state)) {
                //已经发布只能更新，不能保存为草稿
                return ResponseResult.FAILED("已发布的文章，不支持保存为草稿");
            }

        }
        article.setUserId(user.getId());
        article.setUpdateTime(new Date());
        article.setUserAvatar(user.getAvatar());
        article.setUserName(user.getUserName());
        //保存数据
        articleDao.save(article);
        //todo:保存得到solr的数据库里
//       solrService.addArticle(article);
        //打散标签，入库统计
        setUpLabels(article.getLabel());
        //从redis里删除文章列表
        redisUtils.del(Constants.Article.KEY_ARTICLE_LIST_FIRST_PAGE);
        //返回结果，只用一种case使用到这个id
        //如果要做程序自动保存为草稿(比如每30秒保存一次，就需要加这个id)

        return ResponseResult.SUCCESS(Constants.Article.STATE_DRAFT.equals(state) ? "草稿保存成功" : "文章发布成功").setData(article.getId());
    }

    private void setUpLabels(String labels) {
        List<String> labelList = new ArrayList<>();
        if (labels.contains("-")) {
            labelList.addAll(Arrays.asList(labels.split("-")));
        } else {
            labelList.add(labels);
        }
        //入库统计
        for (String label : labelList) {
            Label targetLabel = labelDao.findOneByName(label);
//            if (targetLabel == null) {
//                targetLabel = new Label();
//                targetLabel.setId(idWorker.nextId() + "");
//                targetLabel.setCount(0);
//                targetLabel.setName(label);
//                targetLabel.setCreateTime(new Date());
//            }
//            long count = targetLabel.getCount();
//            targetLabel.setCount(++count);
//            targetLabel.setUpdateTime(new Date());
            int result = labelDao.updateCountByName(label);
            if (result == 0) {
                targetLabel = new Label();
                targetLabel.setId(idWorker.nextId() + "");
                targetLabel.setCount(1);
                targetLabel.setName(label);
                targetLabel.setCreateTime(new Date());
                targetLabel.setUpdateTime(new Date());
                labelDao.save(targetLabel);
            }

        }
    }

    /**
     * 获取文章列表
     *
     * @param page
     * @param pageSize
     * @param state
     * @param keyword    搜索关键字
     * @param categoryId 分类id
     * @return
     */
    @Override
    public ResponseResult articleList(int page, int pageSize, String keyword, String categoryId, String state) {
        //处理page 和pageSize
        page = checkPage(page);
        pageSize = checkPageSize(pageSize);
        //数据第一页做缓存


        String articleListJson = (String) redisUtils.get(Constants.Article.KEY_ARTICLE_LIST_FIRST_PAGE);
        //redis里不为空，就使用redis里面的
        if (!StrUtil.isEmpty(articleListJson) && page == 1) {
            PageList<ArticleNoAll> result = gson.fromJson(articleListJson, new TypeToken<PageList<ArticleNoAll>>() {
            }.getType());
            log.info("article list form redis");
            return ResponseResult.SUCCESS("获取文章列表").setData(result);
        }
        //redis为空，就从数据库里查询，并保存到redis
        Sort sort = Sort.by(Sort.Direction.DESC, "createTime");
        Pageable pageable = PageRequest.of(page - 1, pageSize, sort);
        Page<ArticleNoAll> all = articleNoAllDao.findAll(new Specification<ArticleNoAll>() {
            @Override
            public Predicate toPredicate(Root<ArticleNoAll> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder cb) {
                List<Predicate> predicates = new ArrayList<>();
                //判断是否有传参数
                if (!StrUtil.isEmpty(state)) {
                    Predicate statePre = cb.equal(root.get("state").as(String.class), state);
                    predicates.add(statePre);

                }
                if (!StrUtil.isEmpty(categoryId)) {
                    Predicate categoryIdPre = cb.equal(root.get("categoryId").as(String.class), categoryId);
                    predicates.add(categoryIdPre);
                }
                if (!StrUtil.isEmpty(keyword)) {
                    //模糊查询
                    Predicate titlePre = cb.like(root.get("title").as(String.class), "%" + keyword + "%");
                    predicates.add(titlePre);
                }
                Predicate[] preArray = new Predicate[predicates.size()];
                predicates.toArray(preArray);
                return cb.and(preArray);
            }
        }, pageable);

        PageList<ArticleNoAll> result = new PageList<>();
        //解析
        result.parsePage(all);
        //缓存15分钟
        if (page == 1) {
            redisUtils.set(Constants.Article.KEY_ARTICLE_LIST_FIRST_PAGE,
                    gson.toJson(result), Constants.TimeValueInSecond.MIN_15);
        }


        return ResponseResult.SUCCESS("获取文章列表成功").setData(result);
    }

    /**
     * 获取文章详情
     * 如果有审核机制，审核的文章只有管理员和作者自己可以获取
     * 有草稿，删除，置顶的，已经发布的
     * 删除的不能获取，其他可以获取
     * <p>
     * 统计文章的阅读量
     * 要精确一定的话对ip进行处理，如果时同一个ip,则不保存
     * 先把阅读量在reids里更新
     * 文章也会在redis里缓存一份，比如说10分钟
     * 当文章每有的时候，从mysql中取，这时同时更新阅读量
     * 10分钟以后，下一次访问时更新一次
     * </P>
     *
     * @param articleId
     * @return
     */
    @Override
    public ResponseResult getArticleById(String articleId) {

        //先从redis里获取
        String articleJson = (String) redisUtils.get(Constants.Article.KEY_ARTICLE_CACHE + articleId);
        //如果没有，再去mysql
        if (!StrUtil.isEmpty(articleJson)) {
            log.info("从缓存中拿");
            Article article = gson.fromJson(articleJson, Article.class);
            //增加阅读数量
            redisUtils.incr(Constants.Article.KEY_ARTICLE_VIEW_COUNT + articleId, 1);
            return ResponseResult.SUCCESS("成功").setData(article);
        }

        //查询文章
        Article articleFromDb = articleDao.findOneById(articleId);
        if (articleFromDb == null) {
            return ResponseResult.FAILED("文章不存在");
        }
        //判断文章状态
        String state = articleFromDb.getState();
        //如果是已经发布的和置顶的文章
        if (Constants.Article.STATE_PUBLISH.equals(state) || Constants.Article.STATE_TOP.equals(state)) {
            //正常发布状态才可以增加阅读量
            redisUtils.set(Constants.Article.KEY_ARTICLE_CACHE + articleId,
                    gson.toJson(articleFromDb), Constants.TimeValueInSecond.MIN_5);
            //设置阅读量的key,先从reids里拿，如果reids里没有，就从article获取，并添加到redis里
            String viewCount = (String) redisUtils.get(Constants.Article.KEY_ARTICLE_VIEW_COUNT + articleId);
            if (StrUtil.isEmpty(viewCount)) {
                long newCount = articleFromDb.getViewCount() + 1;
                redisUtils.set(Constants.Article.KEY_ARTICLE_VIEW_COUNT + articleId, String.valueOf(newCount));
            } else {
                //有的话更新到mysql
                long newCount = redisUtils.incr(Constants.Article.KEY_ARTICLE_VIEW_COUNT + articleId, 1);
                articleFromDb.setViewCount(newCount);
                articleDao.save(articleFromDb);
                //todo:更新solr中阅读量
//                solrService.updateArticle(articleId,articleFromDb);
            }
            return ResponseResult.FAILED("获取文章成功").setData(articleFromDb);
        }
        //如果是删除或者草稿，需要管理员
        User user = userService.checkUser();
        //如果不是管理员
        if (user == null || !Constants.User.ROLE_ADMIN.equals(user.getRoles())) {
            //无权访问
            return ResponseResult.PERMISSION_DENIED();
        }
        return ResponseResult.FAILED("获取文章成功").setData(articleFromDb);
    }

    /**
     * 更新文章内容
     * 只支持修改：标题，内容，分类，标签，摘要
     *
     * @param articleId
     * @param updateArticleDTO
     * @return
     */
    @Override
    public ResponseResult updateArticle(String articleId, UpdateArticleDTO updateArticleDTO) {
        //先找出来
        Article articleFormDb = articleDao.findOneById(articleId);
        if (articleFormDb == null) {
            return ResponseResult.FAILED("文章不存在");
        }
        //内容修改
        String title = updateArticleDTO.getTitle();
        if (!StrUtil.isEmpty(title)) {
            articleFormDb.setTitle(title);
        }
        String content = updateArticleDTO.getContent();
        if (!StrUtil.isEmpty(content)) {
            articleFormDb.setContent(content);
        }
        String summary = updateArticleDTO.getSummary();
        if (!StrUtil.isEmpty(summary)) {
            articleFormDb.setSummary(summary);
        }
        String label = updateArticleDTO.getLabel();
        if (!StrUtil.isEmpty(label)) {
            articleFormDb.setLabel(label);
        }
        String categoryId = updateArticleDTO.getCategoryId();
        if (!StrUtil.isEmpty(categoryId)) {
            articleFormDb.setCategoryId(categoryId);
        }
        articleFormDb.setCover(updateArticleDTO.getCover());
        articleFormDb.setUpdateTime(new Date());
        articleDao.save(articleFormDb);
        //返回结果
        return ResponseResult.SUCCESS("文章修改成功");
    }

    /**
     * 删除文章（物理删除）
     *
     * @param articleId
     * @return
     */
    @Override
    public ResponseResult deleteArticleById(String articleId) {
        //要先删除评论的
        //应为评论的articleId，外键是article的id
        commentDao.deleteAllByArticleId(articleId);

        int result = articleDao.deleteAllById(articleId);

        if (result > 0) {
            //从reids删除缓存
            redisUtils.del(Constants.Article.KEY_ARTICLE_CACHE + articleId);
            redisUtils.del(Constants.Article.KEY_ARTICLE_LIST_FIRST_PAGE);
            //todo:删除solr中的内容
//            solrService.deleteArticle(articleId);
            return ResponseResult.SUCCESS("删除成功");
        } else {
            return ResponseResult.FAILED("文章不存在");
        }
    }

    /**
     * 删除通过状态（标记删除）
     *
     * @param articleId
     * @return
     */
    @Override
    public ResponseResult deleteArticleByState(String articleId) {
        int result = articleDao.deleteArticleByState(articleId);
        if (result > 0) {

            //从redis删除缓存
            redisUtils.del(Constants.Article.KEY_ARTICLE_CACHE + articleId);
            redisUtils.del(Constants.Article.KEY_ARTICLE_LIST_FIRST_PAGE);
            //todo:删除solr中的内容
//            solrService.deleteArticle(articleId);
            return ResponseResult.SUCCESS("删除成功");
        }

        return ResponseResult.FAILED("文章不存在");
    }

    /**
     * 置顶
     *
     * @param articleId
     * @return
     */
    @Override
    public ResponseResult topArticle(String articleId) {
        //如果文章的状态必须是已经发布的，如果文章已经置顶了，必须取消置顶
        Article articleFromDb = articleDao.findOneById(articleId);
        if (articleFromDb == null) {
            return ResponseResult.SUCCESS("文章不存在");
        }
        String state = articleFromDb.getState();
        //表示是发布的文章
        if (Constants.Article.STATE_PUBLISH.equals(state)) {
            //置顶
            articleFromDb.setState(Constants.Article.STATE_TOP);
            articleDao.save(articleFromDb);
            return ResponseResult.SUCCESS("置顶成功");
        }
        if (Constants.Article.STATE_PUBLISH.equals(state)) {
            //置顶
            articleFromDb.setState(Constants.Article.STATE_PUBLISH);
            articleDao.save(articleFromDb);
            return ResponseResult.SUCCESS("取消置顶成功");
        }
        return ResponseResult.FAILED("文章不存在");


    }

    /**
     * 获取置顶文章列表
     * 与权限无关
     * 状态必须是置顶
     *
     * @return
     */
    @Override
    public ResponseResult listTopArticle() {
        List<Article> all = articleDao.findAll(new Specification<Article>() {

            @Override
            public Predicate toPredicate(Root<Article> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder cb) {
                return cb.equal(root.get("state").as(String.class), Constants.Article.STATE_TOP);
            }
        });
        return ResponseResult.SUCCESS("获取置顶文章成功").setData(all);
    }

    /**
     * 获取推荐文章，通过标签计算
     *
     * @param articleId
     * @param size
     * @return
     */
    @Override
    public ResponseResult listRecommendArticle(String articleId, int size) {
        //查询文章
        String labels = articleDao.listArticleLabelById(articleId);
        //打撒标签
        List<String> labelList = new ArrayList<>();
        if (labels.contains("-")) {
            labelList.addAll(Arrays.asList(labels.split("-")));
        } else {
            labelList.add(labels);
        }

        //从列表中获取一个标签查询与此相关的文章
        String targetLabel = labelList.get(RandomUtil.randomInt(labelList.size()));
        log.info("targetlabel-------------->" + targetLabel);
        //不查询内容
        List<ArticleNoAll> likeResultList = articleNoAllDao.listArticleByLikeLabel("%" + targetLabel + "%", articleId, size);
        //判断长度
        if (likeResultList.size() < size) {
            //不够数，获取最新的文章作为补充
            int dxSize = size - likeResultList.size();
            List<ArticleNoAll> dxList = articleNoAllDao.listLasteArticleBySize(articleId, dxSize);
            likeResultList.addAll(dxList);
        }

        return ResponseResult.SUCCESS("获取推荐文章成功").setData(likeResultList);
    }

    /**
     * 通过标签获取文章列表
     *
     * @param page
     * @param pageSize
     * @param label
     * @return
     */
    @Override
    public ResponseResult articleListByLabel(int page, int pageSize, String label) {
        page = checkPage(page);
        pageSize = checkPageSize(pageSize);
        Sort sort = Sort.by(Sort.Direction.DESC, "createTime");
        Pageable pageable = PageRequest.of(page - 1, pageSize, sort);
        Page<ArticleNoAll> all = articleNoAllDao.findAll(new Specification<ArticleNoAll>() {
            @Override
            public Predicate toPredicate(Root<ArticleNoAll> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder criteriaBuilder) {
                Predicate labelPre = criteriaBuilder.like(root.get("labels").as(String.class), "%" + label + "%");
                Predicate statePublishPre = criteriaBuilder.equal(root.get("state").as(String.class), Constants.Article.STATE_PUBLISH);
                Predicate stateTopPre = criteriaBuilder.equal(root.get("state").as(String.class), Constants.Article.STATE_TOP);
                Predicate or = criteriaBuilder.or(statePublishPre, stateTopPre);
                return criteriaBuilder.and(or, labelPre);
            }
        }, pageable);
        return ResponseResult.SUCCESS("通过标签获取文章列表成功").setData(all);
    }

    /**
     * 获取标签列表
     *
     * @param size
     * @return
     */
    @Override
    public ResponseResult listLabels(int size) {
        size = checkPageSize(size);
        Sort sort = Sort.by(Sort.Direction.DESC, "count");
        Pageable pageable = PageRequest.of(0, size, sort);
        Page<Label> all = labelDao.findAll(pageable);
        return ResponseResult.SUCCESS("获取标签列表成功").setData(all);
    }
}
